/*
* Svd.cs
* 
* Copyright (c) 2005, dnAnalytics. All rights reserved.
*/

#region Using Directives
using System;
using dnAnalytics.LinearAlgebra;
using dnAnalytics.Math;
using dnAnalytics.Exceptions;
using dnAnalytics.Resources;
#endregion

namespace dnAnalytics.LinearAlgebra.Decomposition
{
    /// <summary>
    /// Class for computing the Singular Value Decomposition of a <see cref="Matrix"/>.
    /// </summary>
    /// <exception cref="NotSupportedException">If the library is running on MS Windows and using <b>CLAPACK</b>
    /// as the <see cref="dnAnalytics.LinearAlgebra.Native.LapackProvider"/>.</exception>
    /// <remarks><b>This class is not supported under MS Windows when using 
    /// the CLAPACK library.</b></remarks>
    public sealed class Svd : Algorithm
    {
        #region Fields
        private ISvd decomp;
        private readonly int rows;
        private readonly int columns;
        #endregion Fields

        #region Constructors
        /// <summary>
        /// Constructs an Svd object for the given matrix.
        /// </summary>
        /// <param name="matrix">The matrix to factor.</param>
        /// <param name="computeVectors">Whether to compute the left and right singular vectors or not.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="matrix"/> is <b>null</b>.</exception>
        public Svd(Matrix matrix, bool computeVectors)
        {
            if (Settings.UseNativeLibrary && Settings.Lapack == dnAnalytics.LinearAlgebra.Native.LapackProvider.Clapack && Environment.OSVersion.Platform != PlatformID.Unix)
            {
                throw new NotSupportedException(Strings.SvdNotSupported);
            }

            if (matrix == null)
            {
                throw new ArgumentNullException("matrix", Strings.NullParameterException);
            }

            decomp = new DenseSvd(matrix, computeVectors);
            rows = matrix.Rows;
            columns = matrix.Columns;
        }

        #endregion Constructors

        #region Public Methods

        /// <summary>
        /// Returns whether the decomposition converged or not.
        /// </summary>
        /// <returns><b>true</b> if the decomposition converged; <b>false</b> otherwise.</returns>
        public bool Converged()
        {
            return decomp.Converged();
        }

        ///<summary>Returns the two norm of the <see cref="Matrix"/>.</summary>
        ///<returns>The 2-norm of the <see cref="Matrix"/>.</returns>     
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public double Norm2()
        {
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            return decomp.Norm2();
        }

        ///<summary>Returns the condition number <b>max(S) / min(S)</b>.</summary>
        ///<returns>The condition number.</returns>
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public double ConditionNumber()
        {
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            return decomp.ConditionNumber();
        }

        ///<summary>Returns the effective numerical matrix rank.</summary>
        ///<returns>The number of non-negligible singular values.</returns>
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public int Rank()
        {
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            return decomp.Rank();
        }

        ///<summary>Returns the left singular vectors as a <see cref="Matrix"/>.</summary>
        ///<returns>The left singular vectors. The matrix will be <b>null</b>,
        ///if <b>computeVectors</b> in the constructor is set to false.</returns>
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public Matrix U()
        {
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            return decomp.U();
        }

        /// <summary>
        /// Copies the left singular vectors into the <paramref name="result"/> <see cref="Matrix"/>.
        /// </summary>
        /// <param name="result">The <see cref="Matrix"/> to copy the left singular vectors into.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="result"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If <paramref name="result"/> doesn't have conforming dimensions.</exception>
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        /// <remarks>The method does nothing if <b>computeVectors</b> in the constructor is set to false.</remarks>
        public void U(Matrix result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }
            if (result.Rows != rows || result.Columns != rows)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            decomp.U(result);
        }

        ///<summary>Returns the right singular vectors as a <see cref="Matrix"/>.</summary>
        ///<returns>The right singular vectors. The matrix will be <b>null</b>,
        ///if <b>computeVectors</b> in the constructor is set to false.</returns>
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        ///<remarks>This is the transpose of the V matrix.</remarks>
        public Matrix VT()
        {
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            return decomp.VT();
        }

        /// <summary>
        /// Copies the right singular vectors into the <paramref name="result"/> <see cref="Matrix"/>.
        /// </summary>
        /// <param name="result">The <see cref="Matrix"/> to copy the right singular vectors into.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="result"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If <paramref name="result"/> doesn't have conforming dimensions.</exception>
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        /// <remarks>The method does nothing if <b>computeVectors</b> in the constructor is set to false.</remarks>
        /// <remarks>This is the transpose of the V matrix.</remarks>
        public void VT(Matrix result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }
            if (result.Rows != columns || result.Columns != columns)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            decomp.VT(result);
        }

        ///<summary>Returns the singular values as a diagonal <see cref="Matrix"/>.</summary>
        ///<returns>The singular values as a diagonal <see cref="Matrix"/>.</returns>        
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public Matrix W()
        {
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            return decomp.W();
        }

        /// <summary>
        /// Resets the <paramref name="result"/> <see cref="Matrix"/> and copies the singular values onto the diagonal.
        /// </summary>
        /// <param name="result"><see cref="Matrix"/> to copy the singular values to.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="result"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If <paramref name="result"/> doesn't have conforming dimensions.</exception>
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public void W(Matrix result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }
            if (result.Rows != rows || result.Columns != columns)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            decomp.W(result);
        }

        ///<summary>Returns the singular values as a <see cref="Vector"/>.</summary>
        ///<returns>the singular values as a <see cref="Vector"/>.</returns>
        ///<exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public Vector S()
        {
            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            return decomp.S();
        }

        /// <summary>
        /// Copies the singular values to the given <see cref="Vector"/>.
        /// </summary>
        /// <param name="result">The <see cref="Vector"/> to copy the singular values into.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="result"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If <paramref name="result"/> doesn't have conforming dimensions.</exception>
        /// <exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public void S(Vector result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }
            int nm = System.Math.Min(rows, columns);

            if (result.Count != nm)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }

            if (!decomp.Converged())
            {
                throw new ConvergenceFailedException();
            }
            decomp.S(result);
        }

        /// <summary>
        /// Finds the least squares solution of <b>A*X = B</b>, where <b>A.Rows &gt;= A.Columns</b>
        /// </summary>
        /// <param name="rightHandSide">The right hand side <see cref="Matrix"/>, <b>B</b>.</param>
        /// <returns>The left hand side <see cref="Matrix"/>, <b>X</b>.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightHandSide"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If the number of rows of the <paramref name="rightHandSide"/> 
        /// matrix differs from the number of rows of the decomposed matrix.</exception>
        /// <exception cref="InvalidOperationException">The left and right singular values were not computed, 
        /// but they are required for this operation.</exception>
        /// <exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public Matrix Solve(Matrix rightHandSide)
        {
            if (rightHandSide == null)
            {
                throw new ArgumentNullException("rightHandSide", Strings.NullParameterException);
            }

            if (rows != rightHandSide.Rows)
            {
                throw new NotConformableException("rightHandSide", Strings.ParameterNotConformable);
            }

            if (rows < columns)
            {
                throw new InvalidMatrixOperationException(Strings.RowsLessThanColumns);
            }

            if (!Converged())
            {
                throw new ConvergenceFailedException();
            }

            return decomp.Solve(rightHandSide);
        }

        /// <summary>
        /// Finds the least squares solution of <b>A*X = B</b>, where <b>A.Rows &gt;= A.Columns</b>
        /// </summary>
        /// <param name="rightHandSide">The right hand side <see cref="Matrix"/>, <b>B</b>.</param>
        /// <param name="result">The left hand side <see cref="Matrix"/>, <b>X</b>.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="rightHandSide"/> or <paramref name="result"/> 
        /// is <b>null</b>.</exception>
        /// <exception cref="NotConformableException"><para>If the number of rows of the <paramref name="rightHandSide"/> 
        /// matrix differs from the number of rows of the decomposed matrix.</para>
        /// <para> - or - </para>
        /// <para>If the <paramref name="rightHandSide"/> matrix has a different number of columns than
        /// the <paramref name="result"/> matrix.</para>
        /// <para> - or - </para>
        /// <para>If the <paramref name="result"/> matrix has a different number of rows than
        /// the number of columns of the decomposed matrix.</para></exception>
        /// <exception cref="InvalidOperationException">The left and right singular values were not computed, 
        /// but they are required for this operation.</exception>
        /// <exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public void Solve(Matrix rightHandSide, Matrix result)
        {
            if (rightHandSide == null)
            {
                throw new ArgumentNullException("rightHandSide", Strings.NullParameterException);
            }

            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }

            if (rows != rightHandSide.Rows)
            {
                throw new NotConformableException("rightHandSide", Strings.ParameterNotConformable);
            }

            if (columns != result.Rows)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }

            if (rightHandSide.Columns != result.Columns)
            {
                throw new NotConformableException(Strings.ParametersNotConformable);
            }

            if (rows < columns)
            {
                throw new InvalidMatrixOperationException(Strings.RowsLessThanColumns);
            }

            if (!Converged())
            {
                throw new ConvergenceFailedException();
            }

            decomp.Solve(rightHandSide, result);
        }


        /// <summary>
        /// Finds the least squares solution of <b>A*x = b</b>, where <b>A.Rows &gt;= A.Columns</b>
        /// </summary>
        /// <param name="rightHandSide">The right hand side vector, <b>b</b>.</param>
        /// <returns>The left hand side <see cref="Vector"/>, <b>x</b>.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightHandSide"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If the size of <paramref name="rightHandSide"/> does not equal
        /// the number of rows of the decomposed matrix.</exception>
        /// <exception cref="InvalidOperationException">The left and right singular values were not computed, 
        /// but they are required for this operation.</exception>
        /// <exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public Vector Solve(Vector rightHandSide)
        {
            if (rightHandSide == null)
            {
                throw new ArgumentNullException("rightHandSide", Strings.NullParameterException);
            }

            if (rows < columns)
            {
                throw new InvalidMatrixOperationException(Strings.RowsLessThanColumns);
            }

            if (rightHandSide.Count != rows)
            {
                throw new NotConformableException("rightHandSide", Strings.ParameterNotConformable);
            }

            if (!Converged())
            {
                throw new ConvergenceFailedException();
            }

            return decomp.Solve(rightHandSide);

        }


        /// <summary>
        /// Finds the least squares solution of <b>A*x = b</b>, where <b>A.Rows &gt;= A.Columns</b>
        /// </summary>
        /// <param name="rightHandSide">The right hand side <see cref="Vector"/>, <b>b</b>.</param>
        /// <param name="result">The left hand side <see cref="Vector"/>, <b>x</b>.</param>
        /// <exception cref="ArgumentNullException">If either <paramref name="rightHandSide"/> or <paramref name="result"/>
        /// is <b>null</b>.</exception>
        /// <exception cref="NotConformableException"><para>If the size of <paramref name="rightHandSide"/> does not equal
        /// the number of rows of the decomposed matrix.</para>
        /// <para> - or - </para>
        /// <para>If the size of <paramref name="result"/> does not equal
        /// the number of columns of the decomposed matrix.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">The left and right singular values were not computed, 
        /// but they are required for this operation.</exception>
        /// <exception cref="ConvergenceFailedException">The decomposition failed to converge to a solution.</exception>
        public void Solve(Vector rightHandSide, Vector result)
        {
            if (rightHandSide == null)
            {
                throw new ArgumentNullException("rightHandSide", Strings.NullParameterException);
            }
            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }

            if (rows < columns)
            {
                throw new InvalidMatrixOperationException(Strings.RowsLessThanColumns);
            }

            if (rightHandSide.Count != rows)
            {
                throw new NotConformableException("rightHandSide", Strings.ParameterNotConformable);
            }

            if (result.Count != columns)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }

            if (!Converged())
            {
                throw new ConvergenceFailedException();
            }

            decomp.Solve(rightHandSide, result);
        }


        #endregion Public Methods

        #region Protected Members

        /// <summary>
        /// Delegates the actual computation to the correct SVD subtype.
        /// </summary>
        protected override void InternalCompute()
        {
            decomp.Compute();
        }
        #endregion Protected Members
    }
}
